-- CompoundM
-- Create by panyl
-- 管理所有的合成规则

module("CompoundM", package.seeall);

-- 所有的规则信息
local rules = {};

-- 根据类型存放规则
local rulesByType = {};

-- 根据材料存放规则
local rulesByMaterial = {};

-- 根据产物存放规则
local rulesByProduct = {};

-- 内部函数声明
local autoCompound;
local whenDoItemBonus;

-- 载入配置表
local _init = false;
local loadCsv = function()
    if _init then return end
    _init = true;

    rules = {};
    rulesByType = {};
    rulesByMaterial = {};
    rulesByProduct = {};

    -- 转换下compound_rule信息表
    local compound_rule_CSV = get_compound_rule_CSV();
    for i = 1, #compound_rule_CSV do
        local ruleId = compound_rule_CSV[i].id
        local type   = compound_rule_CSV[i].type;

        rules[ruleId] = compound_rule_CSV[i]
        rules[ruleId].product = parseProduct(compound_rule_CSV[i].product)

        -- 根据类型存放规则
        if not rulesByType[type] then
            rulesByType[type] = {};
        end
        table.insert(rulesByType[type], ruleId);

        -- 根据产物存放规则
        local product = rules[ruleId].product;
        if compound_rule_CSV[i].auto_compound ~= 1 and product[1][1] == 1 then
            if not rulesByProduct[product[1][2]] then
                rulesByProduct[product[1][2]] = {};
            end
            table.insert(rulesByProduct[product[1][2]], ruleId);
        end

        -- 如果需要自动合成，则根据材料存放规则
        local material = compound_rule_CSV[i].material;
        for _, id in pairs(table.keys(material)) do
            if not rulesByMaterial[id] then
                rulesByMaterial[id] = {};
            end
            table.insert(rulesByMaterial[id], ruleId);
        end
    end

    compound_rule_CSV = {};
end

-- 模块初始化
function init()
    loadCsv();

    -- 关注获得物品的事件
    EventMgr.removeAll("CompoundM");
    EventMgr.register("CompoundM", event.BONUS_ITEM, function(args)
        whenDoItemBonus(args);
    end);
end

-- 解析产品
function parseProduct(rule)
    local product = {}
    local index = 1
    local desc = string.explode(rule, "+")
    for _, info in pairs(desc) do
        local arr = string.explode(string.sub(info, 3, -2), ",");
        if string.startWith(info, "1(") then
            -- 这是物品奖励
            product[index] = { 1, tonumber(arr[1]), tonumber(arr[2]), }
            index = index + 1
        elseif string.startWith(info, "2(") then
            -- 这是属性
            product[index] = { 2, arr[1], tonumber(arr[2]), }
            index = index + 1
        end
    end

    return product
end

-- 检索规则信息
function query(ruleId, path)
    local m = rules[ruleId];
    if not m or path == "" or not path then
        return m;
    else
        return m[path];
    end
end

-- 获取合成规则id
function getCompoundRule(classId)
    if rulesByProduct[classId] then
        return rulesByProduct[classId][1];
    else
        return nil;
    end
end

-- 获取自动合成规则id
function getAutoCompoundRule(classId)
    if rulesByMaterial[classId] then
        return rulesByMaterial[classId][1];
    else
        return nil;
    end
end

-- 根据类型获取规则列表
function getRulesByType(type)
    return rulesByType[type];
end

-- 计算合成消耗
function calcCost(ruleId, compoundAmount)
    -- 没有此合成规则
    if not rules[ruleId] then
        return nil
    end

    -- 如果没有传入合成数量，默认取1
    compoundAmount = compoundAmount or 1;

    local cost = {};
    local index = 1;
    local attribCost = query(ruleId, "attrib_cost");

    -- 装备合成消耗降低
    attribCost = FormulaM.invoke("CALC_COMPOUND_MONEY", ME.user, attribCost, query(ruleId, "type"));

    -- 属性消耗
    for attrib, amount in pairs(attribCost) do
        cost[index] = { 2, attrib, amount * compoundAmount, }
        index = index + 1
    end

    -- 物品消耗
    local material = query(ruleId, "material");
    local type = query(ruleId, "type");
    material = FormulaM.invoke("CALC_COMPOUND_MATERIAL", ME.user, material, type);

    for classId, amount in pairs(material) do
        cost[index] = { 1, classId, amount * compoundAmount, }
        index = index + 1
    end

    return cost;
end

-- 获取配置表中的所有规则
function getAllRules()
    return rules;
end

------------------------------
-- 判断能否合成
-- @param ruleId 合成规则
-- @return 如果能够合成，返回true,否则返回错误码
function canDoCompound(ruleId, maxNum)
    -- 没有该规则
    if not rules[ruleId] then
        return false
    end

    -- 判断消耗
    local num = maxNum or 1;
    local ok = true
    local cost = calcCost(ruleId, maxNum)
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品消耗
            ok = ItemM.getAmount(ME.user, arr[2]) >= arr[3];
            if not ok then
                -- 材料不足
                return "material_lack";
            end
        elseif arr[1] == 2 then
            -- 属性消耗
            ok = ME.user:queryAttrib(arr[2]) >= arr[3];
            if not ok then
                -- 属性不足
                return arr[2] .. "_lack";
            end
        end

        -- 有某一项要求没达到
        if not ok then
            return false
        end
    end

    return ok
end

------------------------------------
-- 执行合成
-- @param ruleId 合成规则
-- @param desc   描述
-- @return       合成是否成功
function doCompound(ruleId, desc, num)
    -- 经判断不能合成
    local compoundNum = num or 1;
    if true ~= canDoCompound(ruleId, compoundNum) then
        return false
    end

    -- 先消耗
    local cost = calcCost(ruleId, compoundNum)
    for _, arr in pairs(cost) do
        if arr[1] == 1 then
            -- 物品
            if not ItemM.costAmount(ME.user, arr[2], arr[3]) then
                return false;
            end
        elseif arr[1] == 2 then
            -- 属性
            if not ME.user:costAttrib(arr[2], arr[3]) then
                return false;
            end
        end
    end

    -- 合成成品
    local product = query(ruleId, "product")
    for _, arr in pairs(product) do
        local totalAmount = compoundNum * arr[3];
        local bonus = { arr[1], arr[2], totalAmount };

        BonusM.doBonus(bonus, desc);
    end

    -- 抛出合成事件
    EventMgr.fire(event.DO_COMPOUND, { ["ruleId"] = ruleId, ["isAuto"] = false, ["count"] = compoundNum });
    return true
end

-- 计算最多可合成的数量
function calcCanCompoundAmount(rule)
    local info = query(rule);
    if not info then
        -- 合成规则不存在
        return 0;
    end

    local maxAmount = MAX_VALUE;
    local material = info["material"];
    for _, id in pairs(table.keys(material)) do
        local ownMaterialAmount = ItemM.getAmount(ME.user, id);
        maxAmount = math.min(maxAmount, math.floor(ownMaterialAmount/material[id]));
    end

    -- 属性消耗
    local attribCost = info["attrib_cost"];

    -- 装备合成消耗降低
    attribCost = FormulaM.invoke("CALC_COMPOUND_MONEY", ME.user, attribCost, info["type"]);

    -- 属性消耗
    for attrib, amount in pairs(attribCost) do
        local ownAttribNum = ME.user:queryAttrib(attrib);
        maxAmount = math.min(maxAmount, math.floor(ownAttribNum/amount));
    end

    return maxAmount;
end

-- 获得物品的事件回调
function whenDoItemBonus(args)
    local classId = args["classId"];
    local amount  = args["amount"];

    if amount <= 0 then
        return;
    end

    -- 没有该材料对应的合成规则，不处理
    if not rulesByMaterial[classId] then
        return;
    end

    for _, rule in pairs(rulesByMaterial[classId]) do
        -- 计算最多可合成的数量
        local compoundAmount = calcCanCompoundAmount(rule);

        if compoundAmount >= 1 then
            -- 如果配置了自动合成
            if query(rule, "auto_compound") == TRUE then
                -- 自动合成
                autoCompound(rule, compoundAmount);
            end

            -- 如果配置了合成提醒
            if query(rule, "remind_compound") == TRUE then
                -- 抛出合成提醒事件
                EventMgr.fire(event.COMPOUND_REMIND, {["ruleId"] = rule,});
            end
        end
    end
end

-- 自动合成（这里不再做条件检查）
function autoCompound(ruleId, amount)
    assert(amount > 0);

    local cost = calcCost(ruleId, amount)
    for _, arr in pairs(cost) do
        -- 暂时仅考虑物品消耗
        if arr[1] == 1 then
            -- 物品
            if not ItemM.costAmount(ME.user, arr[2], arr[3]) then
                return false;
            end
        end
    end

    -- 合成成品
    local product = query(ruleId, "product");
    local realProduct = {};
    for _, arr in pairs(product) do
        local totalAmount = amount*arr[3];
        local bonus = { arr[1], arr[2], totalAmount };
        BonusM.doBonus(bonus, "auto_compound");

        table.insert(realProduct, bonus);

        if arr[1] == 1 then
            trace("CompoundM", "自动合成 %d x %d", arr[2], totalAmount);
        elseif arr[1] == 2 then
            trace("CompoundM", "自动合成 %s x %d", arr[2], totalAmount);
        end
    end

    -- 抛出自动合成事件
    EventMgr.fire(event.DO_COMPOUND, {
        ["ruleId"] = ruleId,
        ["isAuto"] = true,
        ["product"] = realProduct,
    });
end

-- 获取合成描述
function getCompoundDesc(ruleId)
    local desc = query(ruleId, "desc");
    if type(desc) ~= "string" then
        return "";
    end

    -- 如果配置了公式，则调用公式计算，否则直接取配置的值
    local prefix = "formula_";
    if string.startWith(desc, prefix) then
         local formulaName = string.sub(desc, string.len(prefix) + 1);
         return FormulaM.invoke(formulaName);
    end

    return desc;
end

-- 计算一键购买的找零奖励, 并执行奖励
function changeBonus(difference)
    if difference <= 0 then
        return {}, false;
    end

    local bonusList = FormulaM.invoke("CALC_BUY_MATERIAL_CHANGE", difference);

    -- 如果当前金币奖励大于1000，抛出奖励事件
    local isFire = false;
    for k, bonus in pairs(bonusList) do
        BonusM.doBonus(bonus, "one_click_buy");
        if bonus[2] == "money" and bonus[3] > 1000 then
            isFire = true;
        end
    end

    return bonusList, isFire;
end

-- 根据当前产品计算合成还需的原料数
function getNeedMaterial(classId)
    local ruleId = getCompoundRule(classId);

    -- 规则信息
    local info = query(ruleId);

    -- 还需要的材料
    local material = FormulaM.invoke("CALC_COMPOUND_MATERIAL", ME.user, info.material, info.type);
    local needMaterial = {};

    for materialClassId, requireAmount in pairs(material) do
        local curAmount = ItemM.getAmount(ME.user, materialClassId);
        local needAmount = requireAmount - curAmount;
        if needAmount > 0 then
            needMaterial[materialClassId] = needAmount;
        end
    end

    return needMaterial;
end